package org.fglxy.mockserver.mock;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.script.ScriptException;

import org.fglxy.mockserver.entity.MockEndEntity;
import org.fglxy.mockserver.entity.MockMessage;
import org.fglxy.mockserver.entity.MockTaskEntity;
import org.fglxy.mockserver.enums.RetCodeEnum;
import org.fglxy.mockserver.exception.MessageException;
import org.fglxy.mockserver.netty.NettyClient;
import org.fglxy.mockserver.netty.WebSocketServer;
import org.fglxy.mockserver.util.JSInvoker;
import org.fglxy.mockserver.util.JSONUtils;
import org.fglxy.mockserver.util.ProtoUtils;
import org.fglxy.mockserver.util.ResourceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.protobuf.Message;
import com.googlecode.protobuf.format.JsonFormat;
import com.googlecode.protobuf.format.JsonFormat.ParseException;

import io.netty.channel.Channel;

/**
 * mock客户端
 * 
 * @author xiaoyangliu
 *
 */
public class MockClient implements IMockEnd {
	private static Logger LOGGER = LoggerFactory.getLogger(MockClient.class);
	// 当前mock客户端的触发任务表
	private CopyOnWriteArrayList<JSInvoker> taskList = new CopyOnWriteArrayList<>();

	private MockEndEntity client;

	private NettyClient nettyClient;

	public MockClient(MockEndEntity client) {
		this.client = client;
	}

	public void init() {
		try {
			doInit();
		} catch (Exception e) {
			throw new MessageException(RetCodeEnum.START_MOCK_FAIL, e);
		}
	}

	private void doInit() throws IOException, ScriptException {
		this.taskList.clear();
		ResourceUtils.readLock(this.getMockId());
		try {
			ProtoUtils.createDescriptor(this.getMockId());
		} finally {
			ResourceUtils.releaseReadLock(this.getMockId());
		}
		List<MockTaskEntity> tasks = ResourceUtils.getTasksByMockId(this.client.getId());
		if (tasks != null) {
			for (MockTaskEntity task : tasks) {
				taskList.add(JSInvoker.getInstance(task.getScript()));
			}
		}
	}

	public void start() {
		try {
			this.nettyClient = new NettyClient(this.client.getServerIp(), this.client.getServerPort());
			this.nettyClient.start(this);
			WebSocketServer.Instance.sendMsg(this.getMockId(), "mockclient启动");
		} catch (InterruptedException e) {
			LOGGER.warn("mock client interrupted:", e);
		}
	}

	public void stop() {
		this.nettyClient.getChannel().close();
		this.nettyClient.release();
	}

	public void restart() {
		this.stop();
		this.init();
		this.start();
	}

	@Override
	public void onReceive(Channel channel, MockMessage msg) {
		WebSocketServer.Instance.sendMsg(this.getMockId(), "接收到消息:cmdno:" + msg.getCmdNo());
		// 遍历所有任务，检查是否有对应触发事件，若有则执行
		for (JSInvoker task : taskList) {
			// 将protobuf消息转为map结构，方便传参给js脚本
			//如果没有给这个消息的cmdNo指定映射，则这里不可能解析出msg，js函数将无法获得msg上下文信息
			@SuppressWarnings("unchecked")
			Map<String, Object> json = msg.getMsg() != null ? 
					JSONUtils.parse(JsonFormat.printToString(msg.getMsg()), Map.class)
					: null;
			try {
				// 扫描调用js脚本。如果对应事件不存在则会走异常分支。低效率的做法，但是本身项目对性能也没啥需求
				task.reset();
				String functionName = "onReceive_" + msg.getCmdNo();
				if (!task.hasFunction(functionName)) {
					continue;
				}
				callFunctionAndSendMessage(channel, task, functionName, json);
			} catch (Exception e) {
				WebSocketServer.Instance.sendMsg(this.getMockId(), "消息构造时出现异常:" + e.getMessage());
				LOGGER.error("exception:", e);
			}
		}
	}

	/*
	 * 当连接建立时的回调消息。这里会扫描所有任务，如果存在触发条件为连接建立的消息则向对端发送
	 * 
	 * @see org.fglxy.mockserver.mock.IMockEnd#onConnect()
	 */
	@Override
	public void onConnect(Channel channel) {
		WebSocketServer.Instance.sendMsg(this.getMockId(), "与服务端连接建立完成");
		// 遍历所有任务，检查是否有对应触发事件，若有则执行
		for (JSInvoker task : taskList) {
			try {
				task.reset();
				String functionName = "onConnect";
				if (!task.hasFunction(functionName)) {
					continue;
				}
				callFunctionAndSendMessage(channel, task, functionName);
			} catch (Exception e) {
				LOGGER.error("exception:", e);
			}
		}
	}

	private void callFunctionAndSendMessage(Channel channel, JSInvoker task, String functionName)
			throws NoSuchMethodException {
		callFunctionAndSendMessage(channel, task, functionName, null);
	}

	private void callFunctionAndSendMessage(Channel channel, JSInvoker task, String functionName,
			@SuppressWarnings("rawtypes") Map params) throws NoSuchMethodException {
		// 调用脚本方法，返回必须为数组形式，数组中每项为一个请求协议。如果只需要做一个请求，则数组中只有一项即可
		task.callFunction(functionName, params).forEach((str, obj) -> {
			@SuppressWarnings("unchecked")
			Map<String, Object> script = (Map<String, Object>) obj;
			// 拼接报文
			int cmdNo = Integer.valueOf(script.get("cmdNo").toString());
			// 这里的协议必须指定，否则报错
			Message.Builder builder = ProtoUtils.getBuilderByCmdNo(this.getMockId(), cmdNo);
			String datas = JSONUtils.toJson(script.get("datas"));
			try {
				JsonFormat.merge(datas, builder);
			} catch (ParseException e) {
				WebSocketServer.Instance.sendMsg(this.getMockId(), "格式化对端消息时出现异常:" + e.getMessage());
				LOGGER.error("format message error:", e);
			}
			Message proto = builder.build();
			WebSocketServer.Instance.sendMsg(this.getMockId(), "向对端发送消息:cmdNo:" + cmdNo + ";proto:" + proto);
			// 发送对端消息
			channel.writeAndFlush(new MockMessage(cmdNo, proto));
		});
	}

	@Override
	public void onConnectFinish(Channel channel) {
		this.nettyClient.release();
	}

	@Override
	public String getMockId() {
		return this.client.getId();
	}


}
